/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package lifeed;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import unito.likir.Application;
import unito.likir.Node;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.LinkedList;
import java.util.Locale;
import unito.likir.NodeId;
import unito.likir.io.FileManager;
import unito.likir.routing.Contact;
import unito.likir.storage.StorageEntry;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

/**
 *
 * @author grillo
 */
public class LiFeed {
	//input reader
	private static InputStreamReader inReader = new InputStreamReader(System.in);
	private static BufferedReader bufReader = new BufferedReader(inReader);

    private static Application appl = new Application();
    private static FileManager fileManager = new FileManager();
    private static Node n;
    private static CipherTools cipherTools;

    public static void main(String[] args) {

        Document dom = null;
        List<ItemRSS> rssList = new LinkedList();
        //register the application to a new node (the new node will be added to the environment)
		//n = appl.registerNode("ApplicationNode" + String.valueOf(Math.ceil(Math.random() * 20000)), 10000);
		n = appl.registerNode("ApplicationNode", 10000);

		//initialize the new node
		try
		{
			n.init(); //the new node is initialized (startup + bootstrap)

            //STORE NECESSARIE PER AVERE I FILE DI SETTINGS E DI NEWS ALL'AVVIO DELLA RETE
            String path = "." + File.separator + "Contents" + File.separator + "applicationnode_settings.xml";
            n.put(new File(path), "settings", 360000);
			path = "." + File.separator + "Contents" + File.separator + "applicationnode_pub.xml";
            n.put(new File(path), "news", 360000);
			path = "." + File.separator + "Contents" + File.separator + "applicationnode_priv.xml";
            n.put(new File(path), "news", 360000);
		}
		catch (IOException ioe)
		{
			System.err.println("Error in node initialization");
			System.exit(0);
		}

		while (true)
		{
			printNodeMenu(n);
			int choice = choose(" >> ", 0, 9);
			switch (choice)
			{
                case 1: { //lookup
                    lookup();
                    break;
                }
                case 2: { //find settings & load RSS
                    try {
                        List<FeedRSS> rss = new LinkedList();
                        if(dom == null)
                            dom = findUserSettings();
                        for(Object el : dom.getRootElement().selectNodes("//settings/rss")) {
                            String url = ((Element)el).attribute("url").getText();
                            rss.add(new FeedRSS(url));
                        }
                        int countFeed = 1;
                        int i = 1;
                        Date last_upd = null;
                        for(FeedRSS itemRSS : rss) {
                            Element rss_setting = (Element)((dom.selectNodes("//settings/rss[position()=" + countFeed + "]")).iterator().next());
                            last_upd = parseDate(rss_setting.attribute("last_upd").getText());
                            for(ItemRSS item : itemRSS.getItems()) {
                                if(last_upd.before(item.getPubDate())) {
                                    if(i < 10)
                                        System.out.println("0" + i + " " + item.getTitle());
                                    else
                                        System.out.println(i + " " + item.getTitle());

                                    rssList.add(item);
                                    i++;
                                }
                            }
                            countFeed++;
                        }
                        if(i == 1)
                            System.out.println("Non sono state pubblicate nuove notizie dal " + last_upd.toString());
                        
                        //aggiornamento last_upd feed
                        Date date = new Date();
                        DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yy HH:mm z", Locale.ENGLISH);
                        
                        for(Object el : dom.selectNodes("//settings/rss"))
                            ((Element)el).setAttributeValue("last_upd", dateFormat.format(date));
                        
                        storeUserSettings(dom);
                        
                    } catch (DocumentException de) {}
                    catch(java.net.MalformedURLException mue) {}
                    break;
                }
                case 3: { //store public
                    int i = 1;
                    for(ItemRSS item : rssList) {
                            if(i < 10)
                                System.out.println("0" + i + " " + item.getTitle());
                            else
                                System.out.println(i + " " + item.getTitle());
                            i++;
                    }
                    if(rssList.size() == 0)
                        System.out.println("Non ci sono nuove news da visualizzare");
                    else {
                        int index = choose("Scegli una notizia: ", 1, rssList.size());
                        store(rssList, index-1, false);
                    }
                    break;
                }
                case 4: { //store private
                    int i = 1;
                    for(ItemRSS item : rssList) {
                            if(i < 10)
                                System.out.println("0" + i + " " + item.getTitle());
                            else
                                System.out.println(i + " " + item.getTitle());
                            i++;
                    }
                    if(rssList.size() == 0)
                        System.out.println("Non ci sono nuove news da visualizzare");
                    else {
                        int index = choose("Scegli una notizia: ", 1, rssList.size());
                        store(rssList, index-1, true);
                    }
                    break;
                }
                case 5: { //find my public
                    findNews(n.getUserId(), false);
                    break;
                }
                case 6: { //fin my private
                    findNews(n.getUserId(), true);
                    break;
                }
                case 7:  { //find other
                    try {
                        System.out.println("Inserisci lo username dell'utente: ");
                        String user = bufReader.readLine();
                        findNews(user, false);
                    } catch (IOException e) {}
                    break;
                }
                case 0: {
                    n.exit(false);
                    System.exit(0);
                    break;
                }
                default:
			}
		}
    }

	private static void lookup()
	{
		try
		{
			System.out.print(" Lookup Key : ");
			String input = bufReader.readLine();
			NodeId key = fileManager.hash(input);
			Collection<Contact> result = n.lookup(key).get();
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.append("LOOKUP result: \n");
			for (Contact c : result)
				stringBuilder.append(c+"\n");
			System.out.println(stringBuilder.toString());
		}
		catch (Exception e)
		{
			System.err.println(" Error! \n" + e);
		}
	}

	private static void store(List<ItemRSS> rssList, int index, boolean priv)
	{
		try
		{
			String type = null;
			String path = "." + File.separator + "Contents" + File.separator + appl.getNode().getUserId();
            if(priv)
                path += "_priv.xml";
            else
                path += "_pub.xml";
			File file = new File(path);

            Document dom = null;
            Element favourite_news = null;
            if(file.exists()) {
                if(priv)
                    dom = DocumentHelper.parseText(new String(cipherTools.decrypt(readFile(file))));
                else
                    dom = DocumentHelper.parseText(new String(readFile(file)));
                favourite_news = dom.getRootElement();
            } else {
                dom = DocumentHelper.createDocument();
                favourite_news = dom.addElement("favourite_news");
            }
            Element news = favourite_news.addElement("news");
            news.addAttribute("title", rssList.get(index).getTitle());
            news.addAttribute("description", rssList.get(index).getDescription());
            news.addAttribute("link", rssList.get(index).getLink());
            news.addAttribute("pubdate", rssList.get(index).getPubDate().toString());
            FileOutputStream file2 = new FileOutputStream(file);
            if(priv)
                file2.write(cipherTools.encrypt(dom.asXML().getBytes()));
            else
                file2.write(dom.asXML().getBytes());
            file2.close();

            int result = n.put(file, "news", 360000).get();
			System.out.println("Content stored in "+result+" nodes");
		}
		catch(Exception e)
		{
			System.err.println("Error! \n" + e);
		}
	}

    private static void findNews(String nodeId, boolean priv)
	{
		try
		{
            String news = "";
            String fileName = nodeId;
            if(priv) {
                fileName += "_priv.xml";

                if(cipherTools == null) {
                    System.out.println("Insert your key (almeno 8 caratteri): ");
                    String password = bufReader.readLine();
                    cipherTools = new CipherTools(password);
                }
            }
            else
                fileName += "_pub.xml";

            Collection<StorageEntry> result = n.get(fileManager.hash(fileName),"news",null,true,10).get();
            if(!result.isEmpty()) {
                if(priv)
                    news = new String(cipherTools.decrypt(result.iterator().next().getContent().getValue()));
                else
                    news = new String(result.iterator().next().getContent().getValue());
                Document domNews = DocumentHelper.parseText(news);
                int i = 0;
                for(Object el : domNews.selectNodes("//favourite_news/news")) {
                    System.out.println("NEWS " + (i+1));
                    System.out.println("Titolo: " + ((Element)el).attribute("title").getText());
                    System.out.println("Link: " + ((Element)el).attribute("link").getText());
                    System.out.println("Data: " + ((Element)el).attribute("pubdate").getText() + "\n");
                    i++;
                }
            } else
                System.out.println("Utente o file non trovato");
		}
		catch (Exception e)
		{
			System.err.println(" Errore! \n" + e);
		}
	}

    private static Document findUserSettings()
	{
		try
		{
            String settings = "";
            if(cipherTools == null) {
                System.out.println("Insert your key (almeno 8 caratteri): ");
                String password = bufReader.readLine();
                cipherTools = new CipherTools(password);
            }
            
			Collection<StorageEntry> result = n.get(fileManager.hash(appl.getNode().getUserId()+"_settings.xml"),"settings", appl.getNode().getUserId(),true,10).get();
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.append("LOOKUP results : \n");
            settings = new String(cipherTools.decrypt(result.iterator().next().getContent().getValue()));
            stringBuilder.append(settings+"\n");
			System.out.println(stringBuilder.toString());

            return DocumentHelper.parseText(settings);
		}
		catch (Exception e)
		{
			System.err.println(" Errore! \n" + e);
            return null;
		}
	}

	private static void storeUserSettings(Document dom)
	{
		try
		{
			String type = null;
			String path = "." + File.separator + "Contents" + File.separator + appl.getNode().getUserId() + "_settings.xml";
			File file = new File(path);

            FileOutputStream out = new FileOutputStream(file);

            byte[] storePlainByte = dom.asXML().getBytes();
            byte[] storeCiphByte = cipherTools.encrypt(storePlainByte);

            out.write(storeCiphByte);
            out.close();

            int result = n.put(file, "settings", 360000).get();
			System.out.println("Content stored in "+result+" nodes");
		}
		catch(Exception e)
		{
			System.err.println("Error! \n" + e);
		}

	}

    private static int choose(String requestString, int min, int max)
	{
		String input;
		int inputNumber = -1;
		while (inputNumber <  min || inputNumber > max)
		{
			try
			{
				System.out.print(requestString);
				input = bufReader.readLine();
				inputNumber = Integer.parseInt(input);
			}
			catch(Exception e)
			{
				inputNumber = -1;
			}
		}
		return inputNumber;
	}

	private static void printNodeMenu(Node n)
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append("\n+------------------------------------+\n");
		stringBuilder.append("             NODE: "+n.getUserId()+"       \n");

		stringBuilder.append("+------------------------------------+\n");
		stringBuilder.append("|  1 - Lookup                        |\n");
		stringBuilder.append("|  2 - Load Settings & Read RSS      |\n");
		stringBuilder.append("|  3 - Choose & Store (public)       |\n");
		stringBuilder.append("|  4 - Choose & Store (private)      |\n");
		stringBuilder.append("|  5 - Read my favourite (public)    |\n");
		stringBuilder.append("|  6 - Read my favourite (private)   |\n");
		stringBuilder.append("|  7 - Read other's favourite        |\n");
		stringBuilder.append("|  0 - exit                          |\n");
		stringBuilder.append("+------------------------------------+");
		System.out.println(stringBuilder.toString());
	}

    // ############## MOTODI PRIVATE ######################
	private static Date parseDate(String d) {
        String[] date_formats = { "yyyy-MM-dd'T'kk:mm:ss'Z'", // ISO
			"yyyy-MM-dd'T'kk:mm:ssz", // ISO
			"yyyy-MM-dd'T'kk:mm:ss", // ISO
			"EEE, dd MMM yy HH:mm:ss z", // RFC822
			"EEE, dd MMM yy HH:mm z", // RFC822 senza secondi
			"EEE, d MMM yyyy kk:mm:ss z", // RFC2882
			"EEE MMM  d kk:mm:ss zzz yyyy", // ASC
			"EEE, dd MMMM yyyy kk:mm:ss", // Disney Mon, 26 January 2004
											// 16:31:00 ET
			"yyyy-MM-dd kk:mm:ss.0", "-yy-MM", "-yyMM", "yy-MM-dd",
			"yyyy-MM-dd", "yyyy-MM", "yyyy-D", "-yyMM", "yyyyMMdd", "yyMMdd",
			"yyyy", "yyD"

        };
        Date date = null;
		SimpleDateFormat formatter;
		d = d.replaceAll("([-+]\\d\\d:\\d\\d)", "GMT$1"); // Correct W3C times
		d = d.replaceAll(" ([ACEMP])T$", " $1ST"); // Correct Disney times
		for (int i = 0; i < date_formats.length; i++) {
			try {
				formatter = new SimpleDateFormat(date_formats[i], Locale.ENGLISH);
				date = formatter.parse(d);
				break;
			} catch (Exception e) {}
		}

		return date;
	}

    private static byte[] readFile(File file) throws IOException {
        InputStream is = new FileInputStream(file);

        // Get the size of the file
        long length = file.length();

        if (length > Integer.MAX_VALUE) {
            // File is too large
        }

        // Create the byte array to hold the data
        byte[] bytes = new byte[(int)length];

        // Read in the bytes
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length
               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
            offset += numRead;
        }

        // Ensure all the bytes have been read in
        if (offset < bytes.length) {
            throw new IOException("Could not completely read file "+file.getName());
        }

        // Close the input stream and return bytes
        is.close();
        return bytes;
    }
}
